//
//  FTW.cpp
//  Directory
//
//  Created by 陈新唯 on 16/4/20.
//  Copyright © 2016年 陈新唯. All rights reserved.
//

#include "FTW.hpp"
#include <unistd.h>
#include <sys/stat.h>
#include <iostream>
#include <future>
#include "Directory.hpp"

namespace File {
    FTW::FTW() {}

	bool FTW::calculate(const std::string &p) {
		auto path_name = path(std::string(p));
		if (calculate_imp(std::move(path_name))) {
			files();
			return true;
		}
		return false;
	}
	bool FTW::calculate(std::string &&p) {
		auto path_name = path(std::move(p));
		if (calculate_imp(std::move(path_name))) {
			files();
			return true;
		}
		return false;
	}

	// return false if the original path name is invalid
	// else return true
	// note: return value will only be checked when this function is invoked by calculate
	bool FTW::calculate_imp(std::string &&path_name) {
		struct stat s;
		try {
			if (lstat(path_name.c_str(), &s) < 0)
				Unix::throwerrno<FTWError>("FTW lstat error");
		} catch (FTWError &err) {
			std::cerr << err.what() << std::endl;
			return false;
		}
		try {
			// only lock m when call check_update
			m.lock();
			check_update(s);
			m.unlock();
		} catch (FTWError &err) {
			std::cerr << err.what() << std::endl;
			// still return true even the type of file is unknown
			return true;
		}
		if (!S_ISDIR(s.st_mode))
			return true;
		
		Directory d;
		try {
			d.setDirectory(path_name);
		} catch (DirectoryError &err) {
			std::cerr << err.what() << std::endl;
			return true;
		}
		while (auto entry = d.getEntry()) {
			// ignore . and ..
			if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
				continue;
			
			auto new_path_name = path_name;
			new_path_name += "/";
			new_path_name += entry->d_name;
			
			// asynchronous do calculate_imp again for new_path_name
			std::async(std::bind(&FTW::calculate_imp, this, std::placeholders::_1),
					   std::move(new_path_name));
		}
		return true;
	}

	void File::FTW::check_update(const struct stat &s) {
		switch (s.st_mode & S_IFMT) {
			case S_IFREG:
				++counter.regular;
				break;
			case S_IFDIR:
				++counter.directory;
				break;
			case S_IFBLK:
				++counter.block;
				break;
			case S_IFCHR:
				++counter.character;
				break;
			case S_IFIFO:
				++counter.fifo;
				break;
			case S_IFSOCK:
				++counter.socket;
				break;
			case S_IFLNK:
				++counter.symlink;
				break;
			default:
				throw FTWError("not a known type!");
				break;
		}
	}

	void File::FTW::printAnswer() {
		std::cout << "regular files:       " << cal_percent(counter.regular) << "%\n";
		std::cout << "directories:         " << cal_percent(counter.directory) << "%\n";
		std::cout << "block specials:      " << cal_percent(counter.block) << "%\n";
		std::cout << "character specials:  " << cal_percent(counter.character) << "%\n";
		std::cout << "fifos:               " << cal_percent(counter.fifo) << "%\n";
		std::cout << "sockets:             " << cal_percent(counter.socket) << "%\n";
		std::cout << "symbolic links:      " << cal_percent(counter.symlink) << "%\n";
	}
}
